/* This is a command line application to show mem usage of a certain process
 * It accepts a optional pid to specify the process
 */

#include <ctype.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>

// macros
#define PROC_PATH "/proc/"
#define SMAPS     "/smaps"
#define STATUS    "/status"
#define MAX_FN    255
#define MAX_PROCN 255
#define MAX_PSSB  64
#define MAX_PRES  255

// variables
static FILE *fp; // file pointer to /proc/pid/smaps
static DIR *dfd; // directory pointer to /proc/
static char procn[MAX_PROCN];
// static char fn[MAX_FN] = PROC_PATH; // file name of '/proc/pid/smaps'
static char pssb[MAX_PSSB];
static unsigned long summ = 0;

// functions
static void
usage(char *cmd)
{
  printf("Usage: %s [the pid you want to show memusage]\n", cmd);
}

static int
isstrdecimal(char *str)
{
  while (*str) {
    if (!isdigit(*str)) {
      return 0;
    }
    str++;
  }
  return 1;
}

// open a certain file(/proc/pid/file),
// return the file pointer(return NULL if error occurs)
static FILE *
procfp(char *pid, char *file)
{
  FILE *fp;
  char bpath[MAX_FN] = PROC_PATH;

  strcat(bpath, pid);
  strcat(bpath, file);

  if ((fp = fopen(bpath, "r")) == NULL) {
    return NULL;
  }

  return fp;
}

// from file /proc/pid/smaps to read next 'Pss' info
static int
fgetpss(FILE *fp, char *pss)
{
  int c;
  char prefix[MAX_PRES];

  while ((c = fgetc(fp)) != EOF) {
    if (c == '\n') {
      fscanf(fp, "%s", prefix);
      if (strcmp(prefix, "Pss:") == 0) {
        fscanf(fp, "%s", pss);
        return 1; // found and exit
      }
    }
  }

  return EOF;
}

// main function
int main(int argc, char *argv[])
{
  // 0. preprocess the option
  if (argc <= 1 || argc > 2) {
    usage(argv[0]);
    exit(1);
  }
  if (!isstrdecimal(argv[1])) {
    fprintf(stderr, "%s: please input a correct format for pid\n", argv[0]);
    exit(1);
  }

  // 1. atempt access to process information pseudo-filesystem(open /proc)
  if ((dfd = opendir(PROC_PATH)) == NULL) {
    fprintf(stderr, "%s: can't open /proc, please make sure your system has standard FHS.\n", argv[0]);
    exit(2);
  }
  closedir(dfd);

  // 2. atempt access to the specified process file(open /proc/pid/status and /proc/pid/smaps)
  // == why choose file 'status' and 'smaps', please read man proc(5) ==
  // 2.1 find the name of process
  if ((fp = procfp(argv[1], STATUS)) == NULL) {
    fprintf(stderr, "%s: can't open /proc/%s%s, please make sure the pid you typed exists\n", argv[0], argv[1], STATUS);
    exit(3);
  }
  fscanf(fp, "Name: %[^\n]", procn);
  fclose(fp);

  // 2.2 open the file 'smaps'
  if ((fp = procfp(argv[1], SMAPS)) == NULL) {
    fprintf(stderr, "%s: can't open /proc/%s%s, please make sure the pid you typed exists\n", argv[0], argv[1], SMAPS);
    exit(3);
  }

  // 3. read the Pss info to calculate the total actual usage of memory
  // == what and why Pss, please see https://www.golinuxcloud.com/check-memory-usage-per-process-linux/
  while (fgetpss(fp, pssb) != EOF) {
    summ += atoi(pssb);
  }

  // 4. print the result
  printf("[%s] memory usage: %.2lf MB\n", procn, summ / 1024.0);

  // Clean up
  fclose(fp);

  return EXIT_SUCCESS;
}
